package com.wwdx.utils;

import com.wwdx.model.dto.MailDTO;
import org.apache.commons.io.FileUtils;
import org.apache.http.*;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.*;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author liguoliang
 */
public class HttpClientUtils {

	private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
	/**
	 * 连接超时
	 */
	private static final int CONNECT_TIMEOUT = 60000;
	/**
	 * 读取超时
	 */
	private static final int SOCKET_TIMEOUT = 60000;
	/**
	 * 连接中单一请求超时
	 */
	private static final int CONNECTION_REQUEST_TIMEOUT = 60000;

	private static CloseableHttpClient closeableHttpClient;

	static {
		try {
            SSLContext sslcontext = SSLContext.getInstance("SSL");
			//信任所有站点
			X509TrustManager x509TrustManager = new X509TrustManager() {

				@Override
				public X509Certificate[] getAcceptedIssuers() {
					return null;
				}

				@Override
				public void checkServerTrusted(X509Certificate[] chain, String authType) {}

				@Override
				public void checkClientTrusted(X509Certificate[] chain, String authType) {}
			};
			sslcontext.init(null, new TrustManager[] { x509TrustManager }, new SecureRandom());

			MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(200).setMaxLineLength(2000).build();

			ConnectionConfig connectionConfig = ConnectionConfig.custom()
                    .setMalformedInputAction(CodingErrorAction.IGNORE)
                    .setUnmappableInputAction(CodingErrorAction.IGNORE)
                    .setCharset(Consts.UTF_8)
                    .setMessageConstraints(messageConstraints)
                    .build();

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                    .register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE))
                    .build();

            SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();

            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
			poolingHttpClientConnectionManager.setDefaultConnectionConfig(connectionConfig);
			poolingHttpClientConnectionManager.setMaxTotal(400);
			poolingHttpClientConnectionManager.setDefaultMaxPerRoute(40);
			poolingHttpClientConnectionManager.setMaxPerRoute(new HttpRoute(new HttpHost("localhost", 80)), 40);
			poolingHttpClientConnectionManager.setDefaultSocketConfig(socketConfig);
			poolingHttpClientConnectionManager.setValidateAfterInactivity(1000);

			RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setCookieSpec(CookieSpecs.DEFAULT)
                    .setExpectContinueEnabled(true)
                    .setConnectTimeout(CONNECT_TIMEOUT)
                    .setSocketTimeout(SOCKET_TIMEOUT)
                    .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                    .build();

			closeableHttpClient = HttpClients.custom()
                    .setConnectionManager(poolingHttpClientConnectionManager)
                    .setDefaultCookieStore(new BasicCookieStore())
                    .setDefaultCredentialsProvider(new BasicCredentialsProvider())
                    .setDefaultRequestConfig(defaultRequestConfig)
                    .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                    .build();

		} catch (NoSuchAlgorithmException | KeyManagementException e) {
			logger.error("错误信息：{}",e.getMessage());
		}
	}

    /**
     * GET请求
     */
    public static String get(String url) {
        logger.info("发送请求：{}",url);
		return execute(new HttpGet(url));
    }

	/**
	 * POST请求
	 */
	public static String post(String url, Object object) {
        logger.info("发送请求：{}",url);
        HttpPost post = new HttpPost(url);
        //将fields装入表单
        List<Field> fields = getFields(object);
        if (!CollectionUtils.isEmpty(fields)){
            List<NameValuePair> nameValuePairs = new ArrayList<>(fields.size());
            try {
                for (Field field : fields) {
                    nameValuePairs.add(new BasicNameValuePair(field.getName(), field.get(object).toString()));
                }
                UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nameValuePairs, "UTF-8");
                post.setEntity(urlEncodedFormEntity);
                return execute(post);
            } catch (IllegalAccessException | UnsupportedEncodingException e) {
                logger.error("错误信息：{}",e.getMessage());
            }
        }
        return null;
    }

    /**
     * WEBAPI邮件发送
     * @return json
     */
	public static String sendMail(MailDTO dto) {
        HttpPost post = new HttpPost(dto.getUrl());
		// 涉及到附件上传, 需要使用 MultipartEntityBuilder
        Charset charset = Charset.forName("UTF-8");
        MultipartEntityBuilder entity = MultipartEntityBuilder.create();
		entity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
		entity.setCharset(charset);
		ContentType textPlain = ContentType.create("text/plain", charset);
		entity.addTextBody("apiUser", dto.getApiUser(), textPlain);
		entity.addTextBody("apiKey", dto.getApiKey(), textPlain);
		entity.addTextBody("to", dto.getEmail(), textPlain);
		entity.addTextBody("from", dto.getFrom(), textPlain);
		entity.addTextBody("fromName", dto.getFromName(), textPlain);
		entity.addTextBody("subject", dto.getSubject(), textPlain);
		entity.addTextBody("html", dto.getHtml(), textPlain);
		//添加附件
		List<File> files = dto.getAttachments();
        ContentType octecStream = ContentType.create("application/octet-stream", charset);
        files.forEach(file ->  entity.addBinaryBody("attachments", file, octecStream, file.getName()));
		post.setEntity(entity.build());
		return execute(post);
    }

    private static String execute(HttpRequestBase requestBase){
        try (CloseableHttpResponse httpResponse = closeableHttpClient.execute(requestBase)){
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                return EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
            }
        } catch (IOException e) {
            logger.error("错误信息：{}",e.getMessage());
        }finally {
            requestBase.releaseConnection();
        }
        return null;
    }

    /**
     * 发送请求
     * @param url 地址
     * @param message xml
     * @return xml
     */
	public static String jaxRestful(String url, String message) {
		HttpPost post = new HttpPost(url);
        StringEntity stringEntity = new StringEntity(message, ContentType.TEXT_PLAIN.withCharset("UTF-8"));
        post.setEntity(stringEntity);
        return execute(post);
    }

    /**
     * 获取类信息
     */
	private static List<Field> getFields(Object object) {
        Class<?> clazz = object.getClass();
        Class<?> superClass = clazz.getSuperclass();
        List<Field> list = getFields(object,clazz.getDeclaredFields());
        list.addAll(getFields(object,superClass.getDeclaredFields()));
        return list;
	}
	
	private static List<Field> getFields(Object object,Field[] fields){
        List<Field> declaredFields = new ArrayList<>(16);
        try {
            for (Field field : fields) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                if (field.get(object) != null) {
                    declaredFields.add(field);
                }
            }
            return declaredFields;
        }catch (IllegalAccessException e){
            logger.error("错误信息：{}",e.getMessage());
        }
        return Collections.emptyList();
    }

    /**
     * 下载网络图片
     */
    public static void saveDownFile(String url,String path,String fileName){
        logger.info("发送请求：{}",url);
        HttpGet get = new HttpGet(url);
        try (CloseableHttpResponse httpResponse = closeableHttpClient.execute(get)){
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity entity = httpResponse.getEntity();
                InputStream inputStream = entity.getContent();
                //这里不必处理IO流关闭的问题，因为FileUtils.copyInputStreamToFile()方法内部会自动把用到的IO流关掉.
                FileUtils.copyInputStreamToFile(inputStream, new File(path,fileName));
            }
        }catch (IOException e){
            logger.error("错误信息：{}",e.getMessage());
        }finally {
			get.releaseConnection();
		}
    }
}